#include <stdint.h>
#include <stdarg.h>
#include "kernel.h"
#include "mt.h"
#include "pm.h"
#include "heap.h"
#include "vm.h"

sched_pri ____sched_pri[SCHED_PRICNT];

// threads are linked and ordered according to expected wake time.
sched_thread *____sched_threadSleeping = 0;
sched_thread *____sched_threadExpired = 0;

// threads are stored in a adjustable array.
sched_thread  ____sched_threads[SCHED_THREADARRAY_FREE_MAX];
unsigned long ____sched_threads_cnt = SCHED_THREADARRAY_FREE_MAX;
unsigned long ____sched_threads_cntUsed = 0;

sched_thread *____sched_curthread = 0;
unsigned long ____sched_curcr3 = 0;
SCHED_PRI 	  ____sched_curpri = 0;

unsigned long ____sched_ticks = 0;
unsigned long ____sched_jiffies = 0;
unsigned long ____sched_jiffies_pertick = 3;
unsigned long ____sched_jiffies_perthread = 100;

// @common initialization
int sched_initialize(uint32_t contextId)
{
	uint_fast16_t x;
	for(x = 0; x < SCHED_PRICNT; ++x)
	{
		____sched_pri[x].cnt = 0;
		____sched_pri[x].active = 0;
	}
	for(x = 0; x < SCHED_THREADARRAY_FREE_MAX; ++x)
	{
		____sched_threads[x].ustack = 0;
	}
	/// create a thread for the current running context (this one before multi-tasking is enabled!)
	// I assume the first free slot will be used. =)
	____sched_curthread = &____sched_threads[0];

	sched_threadDescription td;
	memset(&td, 0, sizeof(td));
	td.entry = 0;
	td.contextId = contextId;
	td.processId = 0;
	td.priority = 0;	
	td.ustackSize = 0;
	td.kstackSize = 1;
	td.cs = SCHED_KERNEL_CODEDESC;
	td.registers.ds = SCHED_KERNEL_DATADESC;
	td.registers.es = SCHED_KERNEL_DATADESC;
	td.registers.fs = SCHED_KERNEL_DATADESC;
	td.registers.gs = SCHED_KERNEL_DATADESC;
	td.registers.ss = SCHED_KERNEL_STACKDESC;
	td.eflags = SCHED_EFLAGS32;
	td.medaddr = 0;
	sched_create_thread(&td, 0);
	return 1;
}

/// @old functionality
static int ____sched_mt = 0;

int mtenabled()
{
	return ____sched_mt;
}
void mtdisable()
{
	____sched_mt = 0;
}
void mtenable()
{
	____sched_mt = 1;
}

// kill the current process using the PID
THREADID sched_thread_terminate(THREADID id)
{
	____sched_threads[id].flags |= SCHED_THREAD_KILLED;
	return 1;
}
// gets the current pid
THREADID sched_thread_current()
{
	return ____sched_curthread->threadId;
}

// create a thread. (kernel thread use zero for processID) (uses a _cdelc calling convention)
THREADID sched_create_thread(sched_threadDescription *desc, unsigned long argCount, ...)
{
	uint_fast32_t x;
	uint_fast16_t y;
	uint32_t kustack;
	va_list ap;
	va_start( ap, argCount );

	// find free slot in thread array
	for(x = 0; x < ____sched_threads_cnt; ++x)
	{
		// determine if slot is used by knowing that we never use a thread with a stack at memory address zero.
		if(!____sched_threads[x].ustack)
		{
			kdbginfo("found free thread slot; creating.\n");
			____sched_threads[x].processId = desc->processId;
			____sched_threads[x].priority = desc->priority;
			____sched_threads[x].threadId = x;
			if(desc->ustackSize)
			{
				kustack = vm_alloc2(g_k_vmd, g_k_vmd, desc->ustackSize, VM_KERNEL) + 0x1000 * desc->ustackSize;
				kdbginfo("kustack:%x\n", kustack);
			}else{
				____sched_threads[x].ustack = 0;
			}
			____sched_threads[x].kstack = vm_alloc2(g_k_vmd, g_k_vmd, desc->kstackSize, VM_KERNEL) + 0x1000 * desc->kstackSize;
			____sched_threads[x].flags = 0;
			____sched_threads[x].cr3 = desc->contextId;
			if(desc->ustackSize)
			{
				// prepare user stack for entry.
				sched_stackTemplate *ste = (sched_stackTemplate*)
						(kustack - sizeof(sched_stackTemplate) - (argCount) * 4);
				unsigned long *bste32 = (unsigned long*)(kustack - (argCount) * 4);
				for(y = 0; y < argCount; ++y)
				{
					bste32[1+y] = va_arg(ap, unsigned long);
				}
				ste->eax = desc->registers.eax; ste->ebx = desc->registers.ebx; ste->ecx = desc->registers.ecx; 
				ste->edx = desc->registers.edx; ste->ebp = desc->registers.ebp; ste->esi = desc->registers.esi;
				ste->edi = desc->registers.edi;
				ste->gs = desc->registers.gs;
				ste->ds = desc->registers.ds;
				ste->es = desc->registers.es;
				ste->fs = desc->registers.fs;
				ste->cs = desc->cs;;
				ste->eflags = desc->eflags;
				ste->eip = desc->entry;
				ste->ss = desc->registers.ss;
				kdbginfo("copying stack from kernel space to user space.\n");
				// copy stack (which was created in kernel into the destination address space)
				kustack = kustack - desc->ustackSize * 0x1000;
				____sched_threads[x].ustack = vm_copy3(g_k_vmd, g_k_vmd, (uint32_t*)desc->contextId, kustack, desc->ustackSize, VM_USER) + desc->ustackSize * 0x1000 - sizeof(sched_stackTemplate) - argCount * 4;
				ste->_esp = ____sched_threads[x].ustack + sizeof(sched_stackTemplate) - 4;
				// free copy of stack in our space.
				vm_free2(g_k_vmd, g_k_vmd, kustack, desc->ustackSize);
			}
			asm("cli");
			// link into thread class with existing threads; if any.
			if(!____sched_pri[desc->priority].active)
			{
				____sched_pri[desc->priority].active = &____sched_threads[x];
				____sched_threads[x].next = &____sched_threads[x];
				____sched_threads[x].prev = &____sched_threads[x];
			}else{
				____sched_threads[x].next = ____sched_pri[desc->priority].active;
				____sched_threads[x].prev = ____sched_pri[desc->priority].active->prev;

				____sched_pri[desc->priority].active->prev = &____sched_threads[x];
				____sched_threads[x].prev->next = &____sched_threads[x];
			}
			asm("sti; int $0x20;");
			kdbginfo("created thread %x\n", &____sched_threads[x]);
			return (THREADID)x;
		}
	}
	kdbgwarn("out of thread slots.. code not implemented\n");
	return (THREADID)-1;
}

extern unsigned long tick;

/// performs a thread switch only if a certain amount of block ticks have passed.
void sched_switch(void)
{
	++____sched_ticks;
	if(____sched_ticks < ____sched_jiffies_pertick)
	{
		return;
	}
	____sched_ticks = 0;
	sched_switch_core();
}

/// performs a thread switch regardless of how many clock ticks have passed.
void sched_switch_core()
{
	uint_fast16_t x;
	/// @debug: just making sure we are running..
	unsigned short *vmem = (unsigned short*)0xb8000;
	++vmem[0];


	/// @todo: has current thread expired it's current slice of time?
	//if(tick * ____sched_jiffies_pertick >= ___sched_jiffies_perthread)
	//{
		/// @todo: implement priority boost.	
	//}	
	
	// have we been killed?
	if(____sched_curthread->flags & SCHED_THREAD_KILLED)
	{
		kdbgwarn("thread was killed:%x\n", ____sched_curthread);
		// unlink current thread
		____sched_curthread->next->prev = ____sched_curthread->prev;
		____sched_curthread->prev->next = ____sched_curthread->next;
		// we should have been the active thread; if so: rotating the thread list will completely remove us.
		if(____sched_curthread->next == ____sched_curthread)
		{
			____sched_pri[____sched_curpri].active = 0;
		}
	}

	// rotate thread list (newer move forward)
	if(____sched_pri[____sched_curpri].active)
	{
		____sched_pri[____sched_curpri].active = ____sched_pri[____sched_curpri].active->next;
	}

	// look for highest priority thread first.
	for(x = 0; x < SCHED_PRICNT; ++x)
	{
		if(____sched_pri[x].active)
		{
			____sched_curthread = ____sched_pri[x].active;
			____sched_curpri = x;
			kdbginfo("continuation-thread:%x [ustack:%x kstack:%x priority:%x]\n", ____sched_curthread, ____sched_curthread->ustack, ____sched_curthread->kstack, ____sched_curthread->priority);
			if(____sched_curthread->threadId > 0)
			{
				asm("hlt; nop; nop;");
			}
			return;
		}
	}
	// no threads? - we should have had a idle one. (lock CPU)
	kdbgerror("??no idle thread??\n");
	for(;;);
	return;
}










